package kvraft

import (
	`sync`
	`time`

	uuid `github.com/satori/go.uuid`
)

func NeverPanic() {
	if p := recover(); p != nil { // 如果发生异常，就。。。
		DPrintf("never panic get error: %v", p)
	}
}

func Now() time.Duration {
	return time.Duration(time.Now().UnixNano())
}

func GetUUID() string {
	u, e := uuid.NewV4()
	if e != nil {
		panic(e)
	}
	return u.String()
}

type clientRequest interface {
	getClientId() string
	getRequestId() uint64
}

type clientsCount struct {
	m     map[string]uint64
	muMap map[string]*sync.Mutex
	gmu   sync.Mutex
}

func (c *clientsCount) init() {
	c.m = make(map[string]uint64)
	c.muMap = make(map[string]*sync.Mutex)
}

func (c *clientsCount) addIfOk(r clientRequest) ServerError {
	clientId, requestId := r.getClientId(), r.getRequestId()
	if requestId == 0 {
		return OK
	}
	mu := c.getMutex(clientId)
	mu.Lock()
	defer mu.Unlock()
	count, ok := c.m[clientId]
	switch {
	case ok && requestId > count:
		c.m[clientId] = requestId
	case !ok:
		c.m[clientId] = requestId
	case ok && requestId <= count:
		DPrintf("request id: %d not larger than client count: %d", requestId, count)
		return ErrHasCommit
	}
	return OK
}

func (c *clientsCount) getMutex(s string) *sync.Mutex {
	if mu, ok := c.muMap[s]; ok {
		return mu
	}
	c.gmu.Lock()
	defer c.gmu.Unlock()
	if mu, ok := c.muMap[s]; ok {
		return mu
	}
	c.muMap[s] = &sync.Mutex{}
	return c.muMap[s]
}

type CanHash interface {
	Hash() string
}

type answerPlugin struct {
	m  map[string]chan generalReply
	mu sync.Mutex
	wg sync.WaitGroup
}

func (ap *answerPlugin) Init() {
	ap.m = map[string]chan generalReply{}
}

func (ap *answerPlugin) WaitRaft(x CanHash) generalReply {
	k := x.Hash()
	ap.mu.Lock()
	if _, ok := ap.m[k]; ok {
		ap.mu.Unlock()
		panic("key repeat")
	}

	ap.m[k] = make(chan generalReply)
	ap.mu.Unlock()

	defer func() {
		ap.mu.Lock()
		close(ap.m[k])
		delete(ap.m, k)
		ap.mu.Unlock()
	}()

	return <-ap.m[k]
}

func (ap *answerPlugin) RaftDoneWaitRequest(x CanHash, r *generalReply) bool {
	defer NeverPanic()
	k := x.Hash()
	ap.mu.Lock()
	ch, ok := ap.m[k]
	if !ok {
		ap.mu.Unlock()
		return false
	}
	ap.mu.Unlock()

	ap.wg.Add(1)
	ch <- *r
	ap.wg.Wait()
	return true
}

func (ap *answerPlugin) RequestDone() {
	ap.wg.Done()
}
